作者:光光同学22167
連接:https://juejin.im/post/6844903764202094606
來源:掘金
注意以下僅分析瀏覽器部分,Node的EventLoop暫時跳過
單線程語言有甚麼問題,就是一次只能處理一件事,就像是去郵局裡面的一個窗口,一次只能處理一個客戶。但這會導致一個問題:
假設今天我們向服務端請求一個圖片,但請求了很長一段時間,那我們畫面不就卡住了
因此這裡程序員很聰明地將任務分成兩大類
即事件循環,指瀏覽器或是node解決JS單線程阻塞的解決機制(注意兩種不一樣)
在JavaScript裡面任務分成兩種
- 宏任務 (MacroTask):
script
全部代碼- setTimeout
- setInterval
- setImmediate (瀏覽器通常不支持(只有IE10支持))
- I/O
- UI Rendering
- 微任務 (MicroTask):
- Process.nextTick(Node才有)
- Promise (注意executer是同步)
- Object.observe(廢了)
- MutationObserver
Javascript
有一個main thread
主線程和call-stack
調用棧(執行棧)所有的任務都會被放到調用棧等待主線程執行。
- 執行棧在執行完同步任務後,查看執行棧是否為空
- 如果執行棧為空,就會去檢查微任務隊列是否為空,若不為空執行所有微任務
- 如果微任務對列為空,執行宏任務
- 每次單個宏任務執行完畢後,檢查微任務隊列是否為空,如果不為空按先入先出規則,全部執行完微任務
- 設置為任務隊列成null,然後執行宏任務,這樣一直循環
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
.首次執行
MacroTask:
- run script
- setTimeout callback
MicroTask:
- Promise1 then
JS Stack(調用棧) : script(第一次執行程序)
率先查看微任務隊列是否為空,此例還有Promise then(分別是)存在,所以率先執行
以下是執行Promise2 then的情況
MacroTask:
run script
setTimeout callback
Microtasks:
- Promise2 then
JS stack:Promise2 callback
MacroTasks:
- setTimeout callback
Microtasks:
JS stack: setTimeout callback
清空MacroTasks隊列和JS stack
MacroTasks:
Microtasks:
JS stack:
上面那個例子可以到這個網站觀看
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
這張圖可以幫助思考
注意Promise的鍊式調用
比方說這個例子
let outerData = 0
Promise.resolve().then(function (data) {
console.log('promise1');
new Promise((resolve, reject) => {
resolve(5)
}).then(data => { // 鍊式調用需要等上一個then處理完
outerData = data
console.log('promise12');
})
})
Promise.resolve().then(function () {
console.log(outerData); // 這裡很明顯會打印出0(因為會比promise12還早進隊列)
console.log('promise2');
});
console.log('script end');
Promise的executor是同步的(Promise的callback)
new Promise(resolve => {
console.log('我在第一喔')
resolve()
}).then(data => {
console.log('then');
})
console.log('我居然在第二');
async/await (其實是Promise的語法糖)
在底層轉換了Promise跟then的函數
每次我們使用
await
,解釋器都創建一個promise
對象,然後把剩下的async
函數中的操作放到then
回調函數中。
async/await
的實現,離不開Promise
。從字面意思來理解,async
是“異步”的簡寫,而await
是async wait
的簡寫可以認為是等待異步方法執行完成。
賀老師知乎上的一個例子 :
// 可以把這個例子
async function f() {
await p
console.log('ok')
}
// 想像成
function f() {
return RESOLVE(p).then(() => {
console.log('ok')
})
}
分析:
await後面的p:
因為resolve裡面放的是同步函數(所以p會立即執行)
p之後的部分會被當作Promise then 微任務
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
console.log('我跟async1一起喔')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
這裡Chrome老版本跟新版本會有差,但在此我們僅分析最新版本
完成同步部分:
1. 首先打印 script start,接下來定義了兩個函數,並執行了async1
2. 觀看async1函數長怎樣
async function async1() {
await async2() // 這一行相對於執行了resolve(async2())注意這個是同步所以會優先打印
// 底下整個會被放進微任務對列裡面
console.log('async1 end')
console.log('我跟async1一起喔')
}
3. 打印完async2 end,繼續執行看到setTimeout將它新增至宏任務隊列裡
4. 看到new Promise分析一下
new Promise(resolve => {
console.log('Promise') // 這裡發生的是同步的(需要馬上請求數據)
resolve()
})
5. 打印完Promise繼續執行,看到第一個then,放進微任務隊列裡面(注意只有第一個,因為第二個then需要等第一個執行完)
6. 打印script end
目前的宏任務隊列以及微任務
完成異步
1. 優先執行微任務,且按照隊列先進先出的邏輯當然是優先處理async1 await以下的部分,所以打印出
async1 end 跟 我跟async1一起喔
2. 再來執行promise1的部分,打印出promise1,接下來發現之下竟然還有then,因此將promise2放到微任務隊列
3. 做到這裡,發現微任務隊列還有東西,因此繼續處理微任務,打印出promise2
4. 剩下一個宏任務setTimeout,因此打印出setTimeout